package com.huang.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.huang.auth.client.AuthTokenClient;
import com.huang.auth.client.AuthUserClient;
import com.huang.auth.entity.to.AuthUserResourceTO;
import com.huang.auth.entity.vo.ValidationTokenVO;
import com.huang.auth.integration.AuthTokenIntegration;
import com.huang.auth.integration.AuthUserIntegration;
import com.huang.entity.result.HStringResult;
import com.huang.entity.result.ResponseEnum;
import com.huang.entity.result.ResultBuilder;
import com.huang.gateway.entity.IgnoringPath;
import com.huang.gateway.properties.MyGateWayProperties;
import com.huang.gateway.utils.AsyncSendRequestUtils;
import com.huang.gateway.utils.IpUtils;
import com.huang.gateway.utils.OsAndBrowserInfoUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;

/**
 * @author HuangShen
 * @Description 自定义全局过滤器
 * @create 2021-09-04 21:02
 */
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(CustomGlobalFilter.class);

    @Resource(name = AuthUserIntegration.BEAN_NAME)
    private AuthUserClient authUserClient;
    @Resource(name = AuthTokenIntegration.BEAN_NAME)
    private AuthTokenClient authTokenClient;

    @Resource
    private MyGateWayProperties myGateWayProperties;
    private static final AntPathMatcher antPathMatcher = new AntPathMatcher();


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        ServerWebExchange.Builder newExchange = exchange.mutate();
        ServerHttpRequest.Builder newRequest = exchange.getRequest().mutate();
        String path = request.getPath().value();
        String method = request.getMethodValue();
        // 0. 获取客户端 ip 操作系统 位置
        newRequest.header("client-ip", IpUtils.getRealIpAddress(request));
        newRequest.header("location", "");
        newRequest.header("operating-system", OsAndBrowserInfoUtil.getOsInfo(request));

        //1. 需要放行的路径 如果是放行
        if (isPermit(path, method)) return chain.filter(newExchange.request(newRequest.build()).build());
        // 2. 取出header 中的token 鉴权
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst("access-token");
        if (StringUtils.isEmpty(token)) {
            return onError(HttpStatus.OK, exchange, ResultBuilder.failHSingle("身份验证失败"));
        }
        // 3. 解析token 得到 用户 账号
        HStringResult<ValidationTokenVO> validationTokenResult = AsyncSendRequestUtils.send(() -> authTokenClient.validationToken(token));
        // token 无效或过期
        if (validationTokenResult.getCode().equals(ResponseEnum.ERROR_ACCESS_TOKEN_TIMEOUT.getCode())) {
            logger.info("token 验证失败 {}", validationTokenResult.getMsg());
            return onError(HttpStatus.OK, exchange, ResultBuilder.failHSingle(ResponseEnum.ERROR_ACCESS_TOKEN_TIMEOUT, validationTokenResult.getMsg()));
        }

        ValidationTokenVO validationToken = validationTokenResult.getData();
        // 4. 根据用户ID 获取用户资源列表
        // 5. 对 path 和 method 进行拼接 得到权限访问符
        AuthUserResourceTO authUserResourceTO = AsyncSendRequestUtils.send(() -> authUserClient.queryResourceByUserId(validationToken.getUserId())).getData();
        // 6. 拼接好的权限 与用户权限列表进行对比
        if (isPermit(method, path, authUserResourceTO)) {
            newRequest.header("userId", authUserResourceTO.getUserId().toString());
            newExchange.request(newRequest.build());
            // 拥有权限 放行
            return chain.filter(newExchange.build());
        }
        // 没有权限
        return onError(HttpStatus.OK, exchange, ResultBuilder.failHSingle(ResponseEnum.ERROR_LOGIN_ACL));


    }

    @Override
    public int getOrder() {
        return 0;
    }


    private Mono<Void> onError(HttpStatus httpStatus, ServerWebExchange exchange, HStringResult<ResponseEnum> res) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
        DataBuffer wrap = response.bufferFactory().wrap(JSON.toJSONString(res).getBytes());
        return response.writeWith(Mono.just(wrap));
    }


    private boolean isPermit(String path, String method) {
        List<IgnoringPath> ignoringPaths = myGateWayProperties.getIgnoringPaths();
        Optional<IgnoringPath> first = ignoringPaths.stream()
                .filter(ignoringPath -> antPathMatcher.matchStart(ignoringPath.getPath(),path))
                .findFirst();
        return first.map(ignoringPath -> ignoringPath.getMethods().contains(method)).orElse(false);

    }

    private boolean isPermit(String method, String path, AuthUserResourceTO authUserResourceTO) {
        // 去掉服务名 和 backend
        return authUserResourceTO.getAuthResourcePOS()
                .stream()
                .anyMatch(s -> StringUtils.equalsIgnoreCase(s.getMethod(), method) && antPathMatcher.matchStart(path.substring(path.indexOf("/", 2)), "/backend" + s.getUrl()));
    }
}
